/*
 When - HTML5 scrolling calendar (https://code.google.com/p/when/)
 Based on: Continuous Calendar (http://madebyevan.com/calendar/) by Evan Wallace
 License: MIT License (see below)

 Copyright (c) 2010 Evan Wallace
 Copyright (c) 2014 Ovidiu Mara

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
*/

// TODO: small resizing problem when today box moves to the next day
// TODO: need a way of exporting/importing data

// Converts colors from RGB to HSL.
// Input: r, g, b between 0 and 1.
// Returns [h, s, l] with s, l between 0 and 1, h between 0 and 359.
// See http://en.wikipedia.org/wiki/HSL_and_HSV for the formulas and what they mean.
function rgb2hsl(r, g, b) {
	r = Math.max(0, Math.min(1, r));
	g = Math.max(0, Math.min(1, g));
	b = Math.max(0, Math.min(1, b));

	var min = Math.min(r, Math.min(g, b));
	var max = Math.max(r, Math.max(g, b));
	var delta = max - min;

	var h, s, l;

	l = (min + max)/2;
	if (delta === 0) {
		h = 0;
		s = 0;
	} else if (max === r) {
		h = ((g - b)/delta) % 6;
	} else if (max === g) {
		h = (b - r)/delta + 2;
	} else {
		h = (r - g)/delta + 4;
	}
	h *= 60;
	if (h < 0) {
		h += 360;
	}
	if (delta !== 0) {
		s = delta/(1 - Math.abs(2*l - 1));
	}
	return [h, s, l];
}

function hsl2rgb(h, s, l) {
	h = Math.max(0, Math.min(359, h));
	s = Math.max(0, Math.min(1, s));
	l = Math.max(0, Math.min(1, l));

	var C = (1 - Math.abs(2*l - 1)) * s;
	h /= 60;
	var X = C * (1 - Math.abs(h % 2 - 1));
	var r, g, b;
	r = g = b = 0;
	if (h >= 0 && h < 1) {
		r = C;
		g = X;
	} else if (h >= 1 && h < 2) {
		r = X;
		g = C;
	} else if (h >= 2 && h < 3) {
		g = C;
		b = X;
	} else if (h >= 3 && h < 4) {
		g = X;
		b = C;
	} else if (h >= 4 && h < 5) {
		r = X;
		b = C;
	} else {
		r = C;
		b = X;
	}
	var m = l - C/2;
	r += m;
	g += m;
	b += m;
	return [r, g, b];
}

function shadeColor(color, ratio) {
	var r, g, b, a;
	var m;
	m = color.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*)?(\d(?:\.\d+))?\)$/i);
	if (m) {
		r = m[1] / 255.0;
		g = m[2] / 255.0;
		b = m[3] / 255.0;
		a = m[4];
	} else {
		return color;
	}

	var hsl = rgb2hsl(r, g, b);
	hsl[2] *= ratio;
	var rgb = hsl2rgb(hsl[0], hsl[1], hsl[2]);
	r = rgb[0];
	g = rgb[1];
	b = rgb[2];

	r = Math.max(0, Math.min(255, Math.round(r * 255)));
	g = Math.max(0, Math.min(255, Math.round(g * 255)));
	b = Math.max(0, Math.min(255, Math.round(b * 255)));
	if (a) {
		return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')';
	} else {
		return 'rgb(' + r + ', ' + g + ', ' + b + ')';
	}
}

function nextItemId() {
	if (!localStorage.globalSeed)
		localStorage.globalSeed	= new Date().getTime() ^ Math.round(Math.random() * Number.MAX_VALUE);
	localStorage.nextId = localStorage.nextId ?
				parseInt(localStorage.nextId) + 1 :
				new Date().getTime() ^ Math.round(Math.random() * Number.MAX_VALUE);

	return 'event_' + localStorage.globalSeed + '_' + localStorage.nextId;
}

function loadWeekStart() {
	// ISO-8601 says the first day of the week is Monday
	if (!localStorage.weekStart)
		localStorage.weekStart = '1';
	weekStart = parseInt(localStorage.weekStart);
	return weekStart;
}

function saveWeekStart() {
	localStorage.weekStart = weekStart + '';
}

// callback expects a list of objects with the itemId and itemValue properties set
function lookupItemsForParentId(parentId, callback) {
	if (localStorage[parentId]) {
		var parentIdsToItemIds = localStorage[parentId].split(',');
		var list = [];

		for (var i in parentIdsToItemIds) {
			var itemId = parentIdsToItemIds[i];
			var itemValue = localStorage[itemId];
			var itemBgColor = localStorage[itemId + 'bgColor'];
			var itemAllDay = localStorage[itemId + 'allDay'];
			list.push({'itemId': itemId,
						  'itemValue': itemValue,
						  'backgroundColor': itemBgColor,
						  'allDay': itemAllDay
					  });
		}

		callback(list);
	}
}

function storeValueForItemId(itemId) {
	var item = document.getElementById(itemId);
	if (item) {
		var parentId = item.parentNode.id;
		localStorage[itemId] = item.value;
		localStorage[itemId + 'bgColor'] = item.style.backgroundColor;
		localStorage[itemId + 'allDay'] = item.style.height === '100%' ? '1' : '0';

		var parentIdsToItemIds = localStorage[parentId] ? localStorage[parentId].split(',') : [];
		var found = false;
		for (var i in parentIdsToItemIds) {
			if (parentIdsToItemIds[i] === itemId) {
				found = true;
				break;
			}
		}
		if (!found) {
			parentIdsToItemIds.push(itemId);
			localStorage[parentId] = parentIdsToItemIds;
		}
	}
}

function removeValueForItemId(itemId) {
	delete localStorage[itemId];
	delete localStorage[itemId + 'bgColor'];
	delete localStorage[itemId + 'allDay'];
	delete localStorage[itemId + '_day'];
	delete localStorage[itemId + '_timer'];

	var item = document.getElementById(itemId);
	if (!item)
		return;
	var parentId = item.parentNode.id;
	if (localStorage[parentId]) {
		var parentIdsToItemIds = localStorage[parentId].split(',');
		for (var i in parentIdsToItemIds) {
			if (parentIdsToItemIds[i] === itemId) {
				parentIdsToItemIds = parentIdsToItemIds.slice(0, i).concat(parentIdsToItemIds.slice(i + 1));
				if (parentIdsToItemIds.length) {
					localStorage[parentId] = parentIdsToItemIds;
				} else {
					delete localStorage[parentId];
				}
				break;
			}
		}
	}
}

var todayDate;
var firstDate;
var lastDate;
var calendarTableElement;
var itemPaddingBottom = (navigator.userAgent.indexOf('Firefox') !== -1) ? 2 : 0;
var months = ['January',
			  'February',
			  'March',
			  'April',
			  'May',
			  'June',
			  'July',
			  'August',
			  'September',
			  'October',
			  'November',
			  'December'];
// For American style set weekStart = 0
var weekStart = loadWeekStart();
var weekEnd = (weekStart + 6) % 7;
var colors = ['#CFE7FF', // blue
			  '#CFFFE7', // green
			  '#FFFFCF', // yellow
			  '#FFE7FF', // purple
			  '#FFBFBB'  // red
		];
var allowUpdates = true;

function idForDate(date) {
	return 'day_' + date.getMonth() + '_' + date.getDate() + '_' + date.getFullYear();
}

function recalculateHeight(itemId) {
	var item = document.getElementById(itemId);
	if (!item)
		return;
	if (item.style.height === '100%')
		return;
	item.style.height = '0px'; // item.scrollHeight doesn't shrink on its own
	item.style.height = item.scrollHeight + itemPaddingBottom + 'px';
}

function keydownHandler() {
	recalculateHeight(this.id);
	if (this.storeTimeout)
		clearTimeout(this.storeTimeout);
	var eventId = this.id;
	this.storeTimeout = setTimeout(function() {
		storeValueForItemId(eventId);
		updateTimerForEvent(eventId);
	}, 100);
}

function checkItem() {
	if (this.value.length === 0) {
		removeValueForItemId(this.id);
		updateTimerForEvent(this.id);
		this.parentNode.removeChild(this);
	}
}

function generateItem(parentId, itemId) {
	var item = document.createElement('textarea');
	var parent = document.getElementById(parentId);
	if (!parent)
		return; // offscreen items aren't generated
	parent.appendChild(item);
	item.id = itemId;
	item.onkeyup = keydownHandler;
	item.onblur = checkItem;
	item.spellcheck = false;
	item.style.backgroundColor = '#CFE7FF';
	item.style.borderColor = shadeColor(item.style.backgroundColor, 0.5);
	item.style.outlineColor = item.style.borderColor;
	return item;
}

document.addEventListener('click', function(e) {
	var parentId = e.target.id;
	if (parentId.indexOf('day_') !== 0)
		return;

	var item = generateItem(parentId, nextItemId());
	recalculateHeight(item.id);
	storeValueForItemId(item.id);
	item.focus();
}, false);

function duplicateEvent(eventId, deltaDays, addIndex) {
	if (eventId.indexOf('event_') !== 0)
		return;

	var event = document.getElementById(eventId);
	if (!event)
		return;

	var day = lookupDayForEvent(eventId);
	if (!day)
		return;

	var dateTokens = day.split('_');
	var eventDate = new Date(parseInt(dateTokens[3]),
							 parseInt(dateTokens[1]),
							 parseInt(dateTokens[2]),
							 0,
							 0,
							 0,
							 0);
	eventDate.setDate(eventDate.getDate() + deltaDays);
	while (eventDate < firstDate) {
		prependWeek();
	}
	while (eventDate > lastDate) {
		appendWeek();
	}

	var newDay = idForDate(eventDate);

	var content	= event.value;
	if (addIndex) {
		var oldIndex = content.match(/ \d+$/g);
		if (oldIndex && oldIndex.length === 1) {
			oldIndex = parseInt(oldIndex[0]);
		} else {
			oldIndex = 1;
			content += ' ' + oldIndex;
			event.value = content;
			storeValueForItemId(event.id);
		}
		var newIndex = oldIndex + 1;
		content = content.replace(/ \d+$/g, ' ' + newIndex);
	}

	var newEvent = generateItem(newDay, nextItemId());
	newEvent.value = content;
	newEvent.style.backgroundColor = event.style.backgroundColor ;
	newEvent.style.borderColor = event.style.borderColor;
	newEvent.style.outlineColor = event.style.outlineColor;
	newEvent.style.height = event.style.height;

	recalculateHeight(newEvent.id);
	storeValueForItemId(newEvent.id);
	smoothScrollToDate(eventDate);
	newEvent.focus();
}

function lookupDayForEvent(eventId) {
	var eventDay = '';
	if (eventId + '_day' in localStorage) {
		return localStorage[eventId + '_day'];
	}
	var eventItem = document.getElementById(eventId);
	if (eventItem) {
		eventDay = eventItem.parentElement.id;
		localStorage[eventId + '_day'] = eventDay;
		return eventDay;
	}
	for (var key in localStorage) {
		if (key.indexOf('day_') === 0) {
			var events = localStorage[key].split(',');
			for (var i in events) {
				if (events[i] === eventId) {
					eventDay = events[i];
					localStorage[eventId + '_day'] = eventDay;
					return eventDay;
				}
			}
		}
	}
	return null;
}

function updateTimerForEvent(eventId) {
	if (eventId.indexOf('event_') !== 0)
		return;

	var day = lookupDayForEvent(eventId);
	if (!day)
		return;

	var timerId = eventId + '_timer';
	var enabled = false;
	if (!eventId in localStorage) {
		enabled = false;
	} else if (timerId in localStorage) {
		enabled = true;
	}
	var timer = document.getElementById(timerId);
	if (enabled) {
		var dateTokens = day.split('_');
		var eventDate = new Date(parseInt(dateTokens[3]),
								 parseInt(dateTokens[1]),
								 parseInt(dateTokens[2]),
								 12,  // add 12 hours to cover for rounding errors or daylight changes
								 0,
								 0,
								 0);
		var deltaWeeks = Math.floor((eventDate - todayDate) / 1000 / 3600 / 24 / 7);
		var deltaDays = Math.floor((eventDate - todayDate) / 1000 / 3600 / 24);

		if (deltaDays <= -1) {
			// Remove expired timer
			toggleTimerForEvent(eventId);
			return;
		}

		var niceTimeout = '';
		if (deltaWeeks >= 2) {
			niceTimeout = deltaWeeks + ' weeks';
			var extraDays = (deltaDays - deltaWeeks * 7);
			if (extraDays) {
				niceTimeout += ',<br/>' + extraDays + ' days';
			}
		} else if (deltaDays > 0) {
			niceTimeout = deltaDays + ' days';
		} else {
			niceTimeout = 'today';
		}

		var clickHandler = 'smoothScrollToDate(new Date(' + eventDate.getFullYear() + ', ' +
				eventDate.getMonth() + ', ' + eventDate.getDate() + '));';

		var timerContent = '<td class="countdown" id="' + timerId + '_col1' + '" onclick="' + clickHandler +'">' + niceTimeout + '</td>' +
				'<td class="countdownspace" id="' + timerId + '_col2' + '" onclick="' + clickHandler +'"></td>' +
				'<td class="countdowntext" id="' + timerId + '_col3' + '" onclick="' + clickHandler +'">' +
				localStorage[eventId] +
				'</td>';
		if (!timer) {
			var timers = document.getElementById('timers');
			timers.innerHTML += '<tr id="' + timerId + '" class="timer">' + timerContent + '</tr>';
			timers.innerHTML += '<tr id="' + timerId + '_rowspace" class="timerspace"><td colspan="3">&nbsp;</tr>';
		} else {
			timer.innerHTML = timerContent;
		}
		var col1 = document.getElementById(timerId + '_col1');
		col1.style.backgroundColor = localStorage[eventId + 'bgColor'];
		col1.style.borderColor = shadeColor(col1.style.backgroundColor, 0.5);
		var col2 = document.getElementById(timerId + '_col2');
		col2.style.backgroundColor = col1.style.backgroundColor;
		col2.style.borderColor = col1.style.borderColor;
		var col3 = document.getElementById(timerId + '_col3');
		col3.style.backgroundColor = col1.style.backgroundColor;
		col3.style.borderColor = col1.style.borderColor;
	} else {
		if (timer) {
			timer.parentNode.removeChild(timer);
		}
		var timerSpace = document.getElementById(timerId + "_rowspace");
		if (timerSpace) {
			timerSpace.parentNode.removeChild(timerSpace);
		}
	}
}

function toggleTimerForEvent(eventId) {
	if (eventId.indexOf('event_') !== 0)
		return;

	var timerId = eventId + '_timer';
	if (timerId in localStorage) {
		delete localStorage[timerId];
	} else {
		localStorage[timerId] = '1';
	}
	updateTimerForEvent(eventId);
}

function updateAllTimers() {
	for (var timerId in localStorage) {
		if (timerId.indexOf('_timer') > 0) {
			var eventId = timerId.replace('_timer', '');
			updateTimerForEvent(eventId);
		}
	}
	// Sort timers
	var timers = [];
	var timer2date = {};
	for (var timerId in localStorage) {
		if (timerId.indexOf('_timer') > 0) {
			var eventId = timerId.replace('_timer', '');
			var day = lookupDayForEvent(eventId);
			if (day) {
				var dateTokens = day.split('_');
				var eventDate = new Date(parseInt(dateTokens[3]),
										 parseInt(dateTokens[1]),
										 parseInt(dateTokens[2]));
				timers.push(timerId);
				timer2date[timerId] = eventDate;
			}
		}
	}
	timers.sort(function (a, b) {
		return timer2date[a] - timer2date[b];
	});
	var container = document.getElementById("timers");
	for (var i = 0; i < timers.length; i++) {
		var timerId = timers[i];
		var timer = document.getElementById(timerId);
		container.insertBefore(timer);
		var timerSpace = document.getElementById(timerId + '_rowspace');
		container.insertBefore(timerSpace);
	}
}

function lastDayInMonth(year, month) {
	var d = new Date(year, month + 1, 0);
	return d.getDate();
}

function generateDay(day, date) {
	var isToday = (date.getDate() === todayDate.getDate() &&
				   date.getMonth() === todayDate.getMonth() &&
				   date.getFullYear() === todayDate.getFullYear());

	day.classList.add('day');
	if (isToday)
		day.classList.add('today');

	if (date.getDay() === 6 || date.getDay() === 0)
		day.classList.add('weekend');

	if (date.getDate() === 1 && date.getDay() !== 1) {
		day.classList.add('firstwithleft');
	}
	if (date.getDate() <= 7) {
		day.classList.add('firstweek');
	}
	if (date.getDate() === lastDayInMonth(date.getFullYear(), date.getMonth()) && date.getDay() !== 0) {
		day.classList.add('lastwithright');
	}
	if (date.getDate() > lastDayInMonth(date.getFullYear(), date.getMonth()) - 7) {
		day.classList.add('lastweek');
	}

	day.id = idForDate(date);
	day.innerHTML = '<span class="dayNumber">' + date.getDate() + '</span>';

	lookupItemsForParentId(day.id, function(items) {
		for (var i in items) {
			var item = generateItem(day.id, items[i].itemId);
			item.value = items[i].itemValue;
			item.style.backgroundColor = items[i].backgroundColor;
			item.style.borderColor = shadeColor(item.style.backgroundColor, 0.5);
			item.style.outlineColor = item.style.borderColor;
			if (items[i].allDay === '1') {
				item.style.height = '100%';
			}
			recalculateHeight(item.id);
		}
	});
}

function getCurrentMonth() {
	var currentYear = 2000;
	var currentMonth = 1;
	var found = 0;
	// var match = 0.0;
	for (var year = firstDate.getFullYear(); !found && year <= lastDate.getFullYear(); year++) {
		for (var month = 0; !found && month < 12; month++) {
			var extra = document.getElementById(year + '_' + month);
			if (!extra)
				continue;
			var y = absoluteOffsetTop(extra);
			var y0 = documentScrollTop() - extra.clientHeight * 3;
			var y1 = documentScrollTop() + window.innerHeight;
			if (y0 <= y && y < y1) {
				// var newMatch = Math.min(y + extra.innerHeight * 4)
				currentYear = year;
				currentMonth = month;
				found = true;
				break;
			}
		}
	}
	var date = new Date;
	date.setFullYear(currentYear);
	date.setMonth(currentMonth);
	date.setDate(1);
	return date;
}

function updateShades() {
	var currentDate = getCurrentMonth();
	var currentYear = currentDate.getFullYear();
	var currentMonth = currentDate.getMonth();

	for (var date = new Date(firstDate); date < lastDate; date.setDate(date.getDate() + 1)) {
		var day = document.getElementById(idForDate(date));
		if (!day) {
			continue;
		}

		var isShaded = !(date.getFullYear() === currentYear &&
						 date.getMonth() === currentMonth);
		if (isShaded) {
			day.classList.add('shaded');
		} else {
			day.classList.remove('shaded');
		}
	}
}

function createMonthName(row, date) {
	var yearMonth = '';
	var monthName = '';

	if (date && date.getDate() === 1) {
		monthName = months[date.getMonth()] + '<br />' + date.getFullYear();
		yearMonth = date.getFullYear() + '_' + date.getMonth();
	}

	var extra = row.insertCell(-1);
	extra.classList.add('extra');
	extra.innerHTML = monthName;
	extra.id = yearMonth;
}

function prependWeek() {
	var week = calendarTableElement.insertRow(0);
	var monthFirst = 0;

	// move firstDate to the beginning of the previous week assuming it is already at the beginning of a week
	do {
		firstDate.setDate(firstDate.getDate() - 1);
		if (firstDate.getDate() === 1) {
			monthFirst = new Date(firstDate);
		}

		var day = week.insertCell(0);
		generateDay(day, firstDate);
	} while (firstDate.getDay() !== weekStart);

	createMonthName(week, monthFirst);
}

function appendWeek() {
	var week = calendarTableElement.insertRow(-1);
	var monthFirst = '';

	// move lastDate to the end of the next week assuming it is already at the end of a week
	do {
		lastDate.setDate(lastDate.getDate() + 1);
		if (lastDate.getDate() === 1) {
			monthFirst = new Date(lastDate);
		}

		var day = week.insertCell(-1);
		generateDay(day, lastDate);
	} while(lastDate.getDay() !== weekEnd)

	createMonthName(week, monthFirst);
}

function absoluteOffsetTop(element) {
	// find the y position by working up the DOM tree
	var y = element.offsetTop;
	while (element.offsetParent && element.offsetParent !== document.body) {
		element = element.offsetParent;
		y += element.offsetTop;
	}
	return y;
}

function scrollPositionForElement(element) {
	var y = absoluteOffsetTop(element);
	var clientHeight = element.clientHeight;

	return y - element.clientHeight * 0.25;
}

function scrollToDate(date) {
	while (date < firstDate) {
		prependWeek();
	}
	while (date > lastDate) {
		appendWeek();
	}

	date = getNiceDate(date);

	window.scrollTo(0, scrollPositionForElement(document.getElementById(idForDate(date))));
}

function scrollToToday() {
	scrollToDate(todayDate);
}

function smoothScrollToToday() {
	smoothScrollToDate(todayDate);
}

function documentScrollTop() {
	scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
	return scrollTop;
}

function documentScrollHeight() {
	var scrollHeight = document.body.scrollHeight;
	if (document.documentElement)
		scrollHeight = Math.max(scrollHeight, document.documentElement.scrollHeight);
	return scrollHeight;
}

var startTime = 0;
var startElement;
var startYOffset;
var goalElement;
var goalDate = 0;

function curve(x) {
	return (x < 0.5) ? (4*x*x*x) : (1 - 4*(1-x)*(1-x)*(1-x));
}

function scrollAnimation() {
	var startY = scrollPositionForElement(startElement) - startYOffset;
	var goalY = scrollPositionForElement(goalElement);

	var percent = (new Date() - startTime) / 300;

	if (percent > 1) {
		window.scrollTo(0, goalY);
		goalDate = 0;
		document.getElementById('debug').innerText = "done!";
	} else {
		window.scrollTo(0, Math.round(startY + (goalY - startY) * curve(percent)));
		setTimeout(scrollAnimation, 10);
	}
}

function getNiceDate(date) {
	// Normally it's more elegant to scroll to the 1st day of the month. However,
	// if today's date does not fit on the screen, scroll to it instead.
	var monthFirst = new Date(date);
	monthFirst.setDate(1);
	var targetDate = monthFirst;
	var day1 = document.getElementById(idForDate(monthFirst));
	var day = document.getElementById(idForDate(date));
	if (absoluteOffsetTop(day1) + window.innerHeight <
			absoluteOffsetTop(day) + day.clientHeight) {
		targetDate = date;
	}
	return targetDate;
}

function smoothScrollToDate(date) {
	while (date < firstDate) {
		prependWeek();
	}
	while (date > lastDate) {
		appendWeek();
	}

	date = getNiceDate(date);

	goalElement = document.getElementById(idForDate(date));
	startElement = document.getElementById(idForDate(getCurrentMonth()));
	startYOffset = scrollPositionForElement(startElement) - documentScrollTop();
	startTime = new Date();
	if (scrollPositionForElement(startElement) - startYOffset !== scrollPositionForElement(goalElement))
		setTimeout(scrollAnimation, 10);
}

function poll() {
	// add more weeks so you can always keep scrolling
	if (documentScrollTop() < 1000) {
		var oldScrollHeight = documentScrollHeight();
		for (var i = 0; i < 8; i++) {
			prependWeek();
		}
		window.scrollBy(0, documentScrollHeight() - oldScrollHeight);
	} else if (documentScrollTop() > documentScrollHeight() - window.innerHeight - 1000) {
		for (var i = 0; i < 8; i++) {
			appendWeek();
		}
	}
	updateShades();

	// update today when the date changes
	var newTodayDate = new Date;
	if (newTodayDate.getDate() !== todayDate.getDate() ||
		newTodayDate.getMonth() !== todayDate.getMonth() ||
		newTodayDate.getFullYear() !== todayDate.getFullYear()) {
		// TODO: resize all items in yesterday and today because of the border change

		var todayElement = document.getElementById(idForDate(todayDate));
		if (todayElement)
			todayElement.classList.remove('today');

		todayDate = newTodayDate;
		todayDate.setHours(0);
		todayDate.setMinutes(0);
		todayDate.setSeconds(0);

		todayElement = document.getElementById(idForDate(todayDate));
		if (todayElement)
			todayElement.classList.add('today');
		updateAllTimers();
	}
}

function loadCalendarAroundDate(seedDate) {
	calendarTableElement.innerHTML = '';
	firstDate = new Date(seedDate);

	// move firstDate to the beginning of the week
	while (firstDate.getDay() !== weekStart) {
		firstDate.setDate(firstDate.getDate() - 1);
	}

	// set lastDate to the day before firstDate
	lastDate = new Date(firstDate);
	lastDate.setDate(firstDate.getDate() - 1);

	// generate the current week (which is like appending to the current zero-length week)
	appendWeek();

	// fill up the entire window with weeks
	while (documentScrollHeight() <= 2 * window.innerHeight) {
		prependWeek();
		appendWeek();
	}

	// need to let the browser recalculate heights before we start scrolling
	setTimeout(scrollToToday, 50);
}

function keyDown(e) {
	if (/^(input|textarea)$/i.test(e.target.nodeName) ||
			e.target.isContentEditable) {
		if (!e.ctrlKey && e.altKey && !e.shiftKey && (49 <= e.keyCode && e.keyCode <= 53)) {
			// Alt+1, Alt+2 ... Alt+5: change color
			e.target.style.backgroundColor = colors[e.keyCode - 49];
			e.target.style.borderColor = shadeColor(e.target.style.backgroundColor, 0.5)
			e.target.style.outlineColor = e.target.style.borderColor;
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && !e.shiftKey && e.keyCode === 48) {
			// Alt+0: toggle all day event
			if (e.target.style.height === '100%') {
				e.target.style.height = '';
				recalculateHeight(e.target.id);
			} else {
				e.target.style.height = '100%';
			}
			e.preventDefault();
		} else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 27) {
			e.target.blur();
			setTimeout(window.focus, 0);
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && !e.shiftKey && e.keyCode === 84) {
			// Alt+T
			if (e.target) {
				var eventId = e.target.id;
				toggleTimerForEvent(eventId);
				updateAllTimers();
			}
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && !e.shiftKey && e.keyCode === 78) {
			// Alt+N
			if (e.target) {
				var eventId = e.target.id;
				duplicateEvent(eventId, 1, 0);
			}
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && e.shiftKey && e.keyCode === 78) {
			// Alt+Shift+N
			if (e.target) {
				var eventId = e.target.id;
				duplicateEvent(eventId, 1, 1);
			}
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && !e.shiftKey && e.keyCode === 87) {
			// Alt+W
			if (e.target) {
				var eventId = e.target.id;
				duplicateEvent(eventId, 7, 0);
			}
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && e.shiftKey && e.keyCode === 87) {
			// Alt+Shift+W
			if (e.target) {
				var eventId = e.target.id;
				duplicateEvent(eventId, 7, 1);
			}
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && !e.shiftKey && e.keyCode === 66) {
			// Alt+B
			if (e.target) {
				var eventId = e.target.id;
				duplicateEvent(eventId, 14, 0);
			}
			e.preventDefault();
		} else if (!e.ctrlKey && e.altKey && e.shiftKey && e.keyCode === 66) {
			// Alt+Shift+B
			if (e.target) {
				var eventId = e.target.id;
				duplicateEvent(eventId, 14, 1);
			}
			e.preventDefault();
		}

		return;
	}
	if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 34) {
		// PageDown
		var currentDate;
		if (!goalDate) {
			currentDate = getCurrentMonth();
			document.getElementById('debug').innerText = currentDate;
			var day = document.getElementById(idForDate(getCurrentMonth()));
			if (scrollPositionForElement(day) - documentScrollTop() < 100 &&
					scrollPositionForElement(day) - documentScrollTop() > 10) {
				// jump to month 1st
			} else {
				currentDate.setMonth(currentDate.getMonth() + 1);
			}
			goalDate = currentDate;
		} else {
			// Last animation has not finished yet, chain this one after it
			goalDate.setMonth(goalDate.getMonth() + 1);
		}
		document.getElementById('debug').innerText = document.getElementById('debug').innerText + goalDate;
		smoothScrollToDate(goalDate);
		e.preventDefault();
	} else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 33) {
		// PageUp
		if (!goalDate) {
			currentDate = getCurrentMonth();
			document.getElementById('debug').innerText = currentDate;
			var day = document.getElementById(idForDate(getCurrentMonth()));
			if (Math.abs(scrollPositionForElement(day) - documentScrollTop()) < 50) {
				// For small differences between the current position and the 1st, skip to the adjacent month
				currentDate.setMonth(currentDate.getMonth() - 1);
			} else {
				// jump to month 1st
			}
			goalDate = currentDate;
		} else {
			// Last animation has not finished yet, chain this one after it
			goalDate.setMonth(goalDate.getMonth() - 1);
		}
		document.getElementById('debug').innerText = document.getElementById('debug').innerText + goalDate;
		smoothScrollToDate(goalDate);
		e.preventDefault();
	} else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 84) {
		// T
		smoothScrollToToday();
		e.preventDefault();
	} else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 72) {
		// H
		if (document.getElementById('help').style.display !== 'block') {
			hideAllPopups();
			showHelp();
		} else {
			hideHelp();
		}
		e.preventDefault();
	} else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 69) {
		// E
		if (document.getElementById('export').style.display !== 'block') {
			hideAllPopups();
			showExport();
		} else {
			hideExport();
		}
		e.preventDefault();
	} else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 73) {
		// I
		if (document.getElementById('import').style.display !== 'block') {
			hideAllPopups();
			showImport();
		} else {
			hideImport();
		}
		e.preventDefault();
	} else if (e.altKey && e.ctrlKey && e.shiftKey && e.keyCode === 67) {
		// Ctrl+Alt+Shift+C
		if (confirm('You are about to delete ALL your events. Are you sure?')) {
			for (var key in localStorage) {
				delete localStorage[key];
			}
			location.reload();
		}
		e.preventDefault();
	} else if (e.altKey && e.ctrlKey && e.shiftKey && e.keyCode === 65) {
		// Ctrl+Alt+Shift+A
		weekStart = 1 - weekStart;
		saveWeekStart();
		e.preventDefault();
		location.reload();
	} else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode === 27) {
		// Esc
		hideAllPopups();
		e.preventDefault();
	}
}

window.onload = function() {
	window.applicationCache.addEventListener('updateready', function(e) {
		if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
			if (allowUpdates && confirm('A new version of this site is available. Reload page?')) {
				location.reload();
			} else {
				allowUpdates = false;
			}
		}
	}, false);
	// Check for updates every hour
	setInterval(function() {
		if (allowUpdates) {
			try {
				window.applicationCache.update();
			} catch (e) {}
		}
	}, 60 * 60 * 1000);

	loadWeekStart();

	calendarTableElement = document.getElementById('calendar');
	todayDate = new Date;
	todayDate.setHours(0);
	todayDate.setMinutes(0);
	todayDate.setSeconds(0);

	loadCalendarAroundDate(todayDate);
	document.addEventListener('scroll', poll.bind(this), false);
	document.addEventListener('keydown', keyDown.bind(this), false);
	document.getElementById('importFile').addEventListener('change', function(e) {
		var file = e.target.files[0];
		var reader = new FileReader();
		reader.onload = function(e) {
			document.getElementById('importText').value = e.target.result;
		}
		reader.readAsText(file);
	});

	updateAllTimers();
	setInterval(poll, 60 * 1000);
}

function showPopup(name) {
	document.getElementById(name).style.display = 'block';
	var box = document.getElementById(name + 'Box');
	var xMargin = Math.max(window.innerWidth / 20, (window.innerWidth - box.clientWidth)/2);
	var yMargin = Math.max(window.innerHeight / 20, (window.innerHeight - box.clientHeight)/2);
	box.style.marginTop = yMargin + 'px';
	box.style.marginBottom = yMargin + 'px';
	box.style.marginLeft = xMargin + 'px';
	box.style.marginRight = xMargin + 'px';
	box.style.maxHeight = '80%';
}

function hidePopup(name) {
	document.getElementById(name).style.display = 'none';
}

function showHelp() {
	showPopup('help');
}

function hideHelp() {
	hidePopup('help');
}

function showExport() {
	var dictionary = {};

	for (var key in localStorage) {
		if (key.indexOf('day_') === 0 || key.indexOf('event_') === 0) {
			dictionary[key] = localStorage[key];
		}
	}

	document.getElementById('exportText').value = JSON.stringify(localStorage, null, ' ');

	var text = document.getElementById('exportText').value;
	var blob = new Blob([text], {type:'text/plain'});

	var saveLink = document.getElementById('exportLink');
	saveLink.download = 'calendar.txt';
	saveLink.href = window.URL.createObjectURL(blob);

	showPopup('export');
}

function doImport() {
	var s = document.getElementById('importText').value;
	var dictionary = 0;
	try {
		dictionary = JSON.parse(s);
	} catch (e) {
		alert("Parsing error:" + e.toString());
		return;
	}
	for (var key in dictionary) {
		if (key.indexOf('day_') === 0 || key.indexOf('event_') === 0) {
			localStorage[key] = dictionary[key];;
		}
	}
	location.reload();
}

function hideExport() {
	hidePopup('export');
}

function showImport() {
	showPopup('import');
}

function hideImport() {
	hidePopup('import');
}

function hideAllPopups() {
	hideHelp();
	hideImport();
	hideExport();
}
